package spring.fox.bean;


import fox.framework.rpc.client.invocation.proxy.RpcProxy;
import fox.framework.rpc.client.invocation.proxy.RpcProxyFactory;
import fox.framework.rpc.context.RpcApplicationContext;
import fox.framework.rpc.context.definition.RpcDefinition;
import fox.framework.rpc.context.definition.RpcDefinitionBuilder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import spring.fox.annotation.RpcReference;
import spring.fox.config.RpcBeanConfig;
import spring.fox.context.SpringFoxRpcApplicationContext;
import spring.fox.context.SpringFoxRpcObjectFactory;

/**
 * @author cuichao
 * @Description: RpcReferenceFactoryBean
 * @Date: create in 2021/3/8 16:53
 */
public class RpcReferenceFactoryBean extends RpcBeanConfig implements FactoryBean, BeanClassLoaderAware ,EnvironmentAware, BeanFactoryAware , BeanNameAware {

    public static final String RPC_APPLICATION_CONTEXT_KEY = "context";

    public static final String RPC_APPLICATION_CONTEXT_BEAN_NAME = "springFoxRpcApplicationContext";

    private String beanName;

    private volatile RpcDefinition definition;

    private RpcApplicationContext context;

    private Environment environment;

    private ClassLoader classLoader;

    private RpcReference annotation;

    private ConfigurableListableBeanFactory beanFactory;

    public RpcReferenceFactoryBean(RpcReference annotation) {
        this.annotation = annotation;
    }

    @Override
    public Object getObject() throws Exception {
        //check Rpc-Context has support Spring
        checkRpcApplicationContext();
        SpringFoxRpcApplicationContext context = (SpringFoxRpcApplicationContext) this.context;
        //check definition
         RpcDefinition definition = getRpcDefinition();
        //get proxy
        RpcProxyFactory proxyFactory = context.getProxyFactory();
        RpcProxy proxy = proxyFactory.getProxy(definition);
        //create proxy instance
        Object object = proxy.getProxy(classLoader);
        return object;
    }


    private RpcDefinition getRpcDefinition(){
       if(definition != null){
           return definition;
       }
       synchronized (this){
           if(definition != null){
               return definition;
           }
           definition =buildRpcDefinition();
           //beanDefinition set RpcDefinition attr
            beanFactory.getBeanDefinition(beanName).setAttribute(SpringFoxRpcObjectFactory.RPC_DEFINITION_KEY,definition);
       }
       return definition;
    }

    private RpcDefinition buildRpcDefinition(){
        //merge Rpc-Metadata
        RpcDefinitionBuilder builder = RpcDefinitionBuilder.genericRpcDefinition();
        builder.setMetaspace(beanName);
        builder.setServiceInterface(getInterfaceClass());
        builder.setGroup(context.getGlobalGroup());
        builder.setProvider(false);
        return  builder.getDefinition();
    }



    private void checkRpcApplicationContext(){
        String msg = context.getClass().getName()+" is not support Spring";
        Assert.isInstanceOf(SpringFoxRpcApplicationContext.class, context, msg);
    }


    @Override
    public Class<?> getObjectType() {
        return getInterfaceClass();
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    this.getClass().getSimpleName() + " requires a ConfigurableListableBeanFactory: " + beanFactory);
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    public void setContext(RpcApplicationContext context) {
        this.context = context;
    }

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }



}
